﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using TetrisClient.TetrisService;
using System.Threading;
using System.ComponentModel;

namespace TetrisClient
{
    /// <summary>
    /// Class responsible for handling most of the client behaviour.
    /// </summary>
    public partial class TetrisPage : UserControl
    {
        private TetrisWebServiceSoapClient proxy;
        private UpdateScreenThread updateScreenThread;
        private NextTetrominoThread nextTrominoThread;
        private GridManipulator tetrisUtils;

        private string userName;

        /// <summary>
        /// Initializes a new instance of the <see cref="TetrisPage"/> class.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        public TetrisPage(string userName)
        {
            InitializeComponent();

            this.userName = userName;
            proxy = new TetrisWebServiceSoapClient();

            tetrisUtils = new GridManipulator(tetrisBoard);
            nextTrominoThread = new NextTetrominoThread(nextTetrominoBoard);
            updateScreenThread = new UpdateScreenThread(10000, this);

            configureEventKeys();
            configureAsyncReturnMethods();

            gameBoard.LostFocus += new RoutedEventHandler(gameBoard_LostFocus);
            gameBoard.GotFocus += new RoutedEventHandler(gameBoard_GotFocus);

            initializeGame();

            Application.Current.RootVisual = this;
            this.Loaded += (s, e) =>
            {
                this.initFocus();
            };
        }

        /// <summary>
        /// Handles the GotFocus event of the gameBoard control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        private void gameBoard_GotFocus(object sender, RoutedEventArgs e)
        {
            releasePause();
        }

        /// <summary>
        /// Handles the LostFocus event of the gameBoard control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        private void gameBoard_LostFocus(object sender, RoutedEventArgs e)
        {
            pause();
        }

        /// <summary>
        /// Initializes the focus.
        /// </summary>
        private void initFocus()
        {
            System.Windows.Browser.HtmlPage.Plugin.Focus();
            pauseGameButton.Focus();
        }

        /// <summary>
        /// Handles the pauseAndReleaseGame event of the button control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        void button_pauseAndReleaseGame(object sender, RoutedEventArgs e)
        {
            pauseAndRelease();
        }

        /// <summary>
        /// If the screen is paused, it continues the thread responsible for updating the
        /// grid, otherwise, it pauses the thread.
        /// </summary>
        private void pauseAndRelease()
        {
            if (updateScreenThread.isPaused())
            {
                releasePause();
            }
            else
            {
                pause();
            }
        }

        /// <summary>
        /// Handles the startNewGame event of the button control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        void button_startNewGame(object sender, RoutedEventArgs e)
        {
            proxy.FinishGameAsync();
            updateScreenThread.stopThread();
            nextTrominoThread.stopThread();
            initializeGame();
        }

        /// <summary>
        /// Handles the Logout event of the hiperlink control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        void hiperlink_Logout(object sender, RoutedEventArgs e)
        {
            proxy.FinishGameAsync();
            updateScreenThread.stopThread();
            nextTrominoThread.stopThread();

            proxy.CloseAsync();
            this.proxy = null;

            LoginPage loginPage = new LoginPage();
            this.Content = loginPage;
        }

        /// <summary>
        /// Initializes the game.
        /// </summary>
        private void initializeGame()
        {
            stackPanel.Visibility = System.Windows.Visibility.Visible;
            stackPanelGameOver.Visibility = System.Windows.Visibility.Collapsed;
            pauseLabel.Visibility = System.Windows.Visibility.Collapsed;
            pauseGameButton.IsEnabled = true;
            proxy.GetGameConfigurationAsync(userName);
        }

        /// <summary>
        /// Configures the async return methods.
        /// </summary>
        private void configureAsyncReturnMethods()
        {
            proxy.MoveLeftCompleted += new EventHandler<MoveLeftCompletedEventArgs>(proxy_MoveLeftCompleted);
            proxy.MoveRightCompleted += new EventHandler<MoveRightCompletedEventArgs>(proxy_MoveRightCompleted);
            proxy.RotateCompleted += new EventHandler<RotateCompletedEventArgs>(proxy_RotateCompleted);
            proxy.GetBoardCompleted += new EventHandler<GetBoardCompletedEventArgs>(proxy_GetBoardCompleted);
            proxy.MoveDownCompleted += new EventHandler<MoveDownCompletedEventArgs>(proxy_MoveDownCompleted);
            proxy.GetGameConfigurationCompleted += new EventHandler<GetGameConfigurationCompletedEventArgs>(proxy_GetGameConfigurationCompleted);
            proxy.StatusCompleted += new EventHandler<StatusCompletedEventArgs>(proxy_StatusCompleted);
            proxy.GetNextTetrominoCompleted +=new EventHandler<GetNextTetrominoCompletedEventArgs>(proxy_GetNextTetrominoCompleted);
            proxy.FinishGameCompleted += new EventHandler<AsyncCompletedEventArgs>(proxy_FinishGameCompleted);
        }

        void proxy_FinishGameCompleted(object sender, AsyncCompletedEventArgs e)
        {
        }

        /// <summary>
        /// Converts the <see cref="ArrayOfInt"/> provided into a bidimensional array of integer.
        /// </summary>
        /// <param name="matrix">The matrix.</param>
        /// <returns></returns>
        private int[][] toIntMatrix(ArrayOfInt[] matrix)
        {
            int[][] newMatrix = new int[matrix.Length][];

            for (int i = 0; i < matrix.Length; i++)
            {
                newMatrix[i] = matrix[i].ToArray();
            }
            return newMatrix;
        }

        /// <summary>
        /// Updates the screen.
        /// </summary>
        /// <param name="gameStatus">The game status.</param>
        private void updateScreen(GameStatus gameStatus)
        {
            updateScreenThread.updateRefreshTime(gameStatus.refreshTime);
            if (nextTrominoThread.setNextTetromino(gameStatus.nextTetromino))
            {
                proxy.GetNextTetrominoAsync();
            }
            tetrisUtils.updateGameBoard(toIntMatrix(gameStatus.board.ToArray<ArrayOfInt>()));
            updateGameInformation(gameStatus);
        }

        /// <summary>
        /// Handles the StatusCompleted event of the proxy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="StatusCompletedEventArgs"/> instance containing the event data.</param>
        void proxy_StatusCompleted(Object sender, StatusCompletedEventArgs args)
        {
            updateGameInformation(args.Result as GameStatus);
        }

        /// <summary>
        /// Handles the GetNextTetrominoCompleted event of the proxy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="GetNextTetrominoCompletedEventArgs"/> instance containing the event data.</param>
        void proxy_GetNextTetrominoCompleted(Object sender, GetNextTetrominoCompletedEventArgs args)
        {
            nextTrominoThread.updateNextTetromino(toIntMatrix(args.Result.ToArray()));
        }

        /// <summary>
        /// Handles the GetGameConfigurationCompleted event of the proxy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="GetGameConfigurationCompletedEventArgs"/> instance containing the event data.</param>
        void proxy_GetGameConfigurationCompleted(Object sender, GetGameConfigurationCompletedEventArgs args)
        {
            GameConfiguration gameConfiguration = args.Result as GameConfiguration;
            resetScoreInfo();
            updateScoresInfo(gameConfiguration);

            updateScreenThread = new UpdateScreenThread(gameConfiguration.refreshTime, this);
            nextTrominoThread = new NextTetrominoThread(nextTetrominoBoard);
            tetrisUtils.createGameBoard(gameConfiguration.numColumns, gameConfiguration.numRows);
            updateScreenThread.startThread();

            nextTrominoThread.createNextTetrominoBoard(gameConfiguration.numColumnsNextTetromino, gameConfiguration.numRowsNextTetromino);
            nextTrominoThread.startThread();
        }

        /// <summary>
        /// Resets the score information.
        /// </summary>
        private void resetScoreInfo()
        {
            updateLabel(bestScore0, "");
            updateLabel(bestScore1, "");
            updateLabel(bestScore2, "");
            updateLabel(bestScore3, "");
            updateLabel(bestScore4, "");

            updateLabel(globalBestScore0, "");
            updateLabel(globalBestScore1, "");
            updateLabel(globalBestScore2, "");
            updateLabel(globalBestScore3, "");
            updateLabel(globalBestScore4, "");
        }

        /// <summary>
        /// Updates the score information.
        /// </summary>
        /// <param name="gameConfiguration">The game configuration.</param>
        private void updateScoresInfo(GameConfiguration gameConfiguration)
        {
            updateMyBestScoresInfo(gameConfiguration);
            updateBestScoresInfo(gameConfiguration);
        }

        /// <summary>
        /// Updates the best scores information.
        /// </summary>
        /// <param name="gameConfiguration">The game configuration.</param>
        private void updateBestScoresInfo(GameConfiguration gameConfiguration)
        {
            if (0 < gameConfiguration.bestScores.Count)
            {
                updateLabel(globalBestScore0, "" + gameConfiguration.bestScores.ElementAt(0));
            }
            if (1 < gameConfiguration.bestScores.Count)
            {
                updateLabel(globalBestScore1, "" + gameConfiguration.bestScores.ElementAt(1));
            }
            if (2 < gameConfiguration.bestScores.Count)
            {
                updateLabel(globalBestScore2, "" + gameConfiguration.bestScores.ElementAt(2));
            }
            if (3 < gameConfiguration.bestScores.Count)
            {
                updateLabel(globalBestScore3, "" + gameConfiguration.bestScores.ElementAt(3));
            }
            if (4 < gameConfiguration.bestScores.Count)
            {
                updateLabel(globalBestScore4, "" + gameConfiguration.bestScores.ElementAt(4));
            }
        }

        /// <summary>
        /// Updates the best scores info.
        /// </summary>
        /// <param name="gameConfiguration">The game configuration.</param>
        private void updateMyBestScoresInfo(GameConfiguration gameConfiguration)
        {
            if (0 < gameConfiguration.myBestScores.Count && gameConfiguration.myBestScores.ElementAt(0) > 0)
            {
                updateLabel(bestScore0, "" + gameConfiguration.myBestScores.ElementAt(0));
            }
            if (1 < gameConfiguration.myBestScores.Count && gameConfiguration.myBestScores.ElementAt(1) > 0)
            {
                updateLabel(bestScore1, "" + gameConfiguration.myBestScores.ElementAt(1));
            }
            if (2 < gameConfiguration.myBestScores.Count && gameConfiguration.myBestScores.ElementAt(2) > 0)
            {
                updateLabel(bestScore2, "" + gameConfiguration.myBestScores.ElementAt(2));
            }
            if (3 < gameConfiguration.myBestScores.Count && gameConfiguration.myBestScores.ElementAt(3) > 0)
            {
                updateLabel(bestScore3, "" + gameConfiguration.myBestScores.ElementAt(3));
            }
            if (4 < gameConfiguration.myBestScores.Count && gameConfiguration.myBestScores.ElementAt(0) > 0)
            {
                updateLabel(bestScore4, "" + gameConfiguration.myBestScores.ElementAt(4));
            }
        }

        /// <summary>
        /// Updates the score label.
        /// </summary>
        /// <param name="scoreLabel">The score label.</param>
        /// <param name="score">The score.</param>
        private void updateLabel(Label scoreLabel, string score)
        {
            scoreLabel.Content = score;
        }

        /// <summary>
        /// Handles the GetBoardCompleted event of the proxy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="GetBoardCompletedEventArgs"/> instance containing the event data.</param>
        void proxy_GetBoardCompleted(Object sender, GetBoardCompletedEventArgs args)
        {
        }

        /// <summary>
        /// Handles the RotateCompleted event of the proxy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="RotateCompletedEventArgs"/> instance containing the event data.</param>
        private void proxy_RotateCompleted(Object sender, RotateCompletedEventArgs args)
        {
            updateScreen(args.Result as GameStatus);
        }

        /// <summary>
        /// Handles the MoveRightCompleted event of the proxy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="MoveRightCompletedEventArgs"/> instance containing the event data.</param>
        private void proxy_MoveRightCompleted(Object sender, MoveRightCompletedEventArgs args)
        {
            updateScreen(args.Result as GameStatus);
        }

        /// <summary>
        /// Handles the MoveLeftCompleted event of the proxy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="MoveLeftCompletedEventArgs"/> instance containing the event data.</param>
        private void proxy_MoveLeftCompleted(Object sender, MoveLeftCompletedEventArgs args)
        {
            updateScreen(args.Result as GameStatus);
        }

        /// <summary>
        /// Handles the MoveDownCompleted event of the proxy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="MoveDownCompletedEventArgs"/> instance containing the event data.</param>
        private void proxy_MoveDownCompleted(Object sender, MoveDownCompletedEventArgs args)
        {
            updateScreen(args.Result as GameStatus);
        }

        /// <summary>
        /// Updates the game information.
        /// </summary>
        /// <param name="gameStatus">The game status.</param>
        private void updateGameInformation(GameStatus gameStatus)
        {
            contentScoreLabel.Content = gameStatus.score;
            contentLevelLabel.Content = gameStatus.level;
            contentLinesLabel.Content = gameStatus.lines;
            checkGameOver(gameStatus);
        }

        /// <summary>
        /// Configures the event keys.
        /// </summary>
        private void configureEventKeys()
        {
            this.userControlTetris.KeyDown += new KeyEventHandler(tetrisBoard_KeyDown);
        }

        /// <summary>
        /// Moves down.
        /// </summary>
        public void moveDown()
        {
            proxy.MoveDownAsync();
        }

        /// <summary>
        /// Handles the KeyDown event of the tetrisBoard control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="KeyEventArgs"/> instance containing the event data.</param>
        void tetrisBoard_KeyDown(object sender, KeyEventArgs e)
        {
            if (Monitor.TryEnter(this, 0))
            {
                try
                {
                    keyEvent(e.Key);
                }
                finally
                {
                    Monitor.Exit(this);
                }
            }
        }

        /// <summary>
        /// Checks if the game is over and if so, displays the game over screen.
        /// </summary>
        /// <param name="gameStatus">The game status.</param>
        private void checkGameOver(GameStatus gameStatus)
        {
            if (gameStatus.isGameOver || gameStatus.refreshTime < 50)
            {
                proxy.FinishGameAsync();
                updateScreenThread.stopThread();
                nextTrominoThread.stopThread();
                stackPanel.Visibility = System.Windows.Visibility.Collapsed;
                stackPanelGameOver.Visibility = System.Windows.Visibility.Visible;
                myScoreLabel.Content = gameStatus.score;
                pauseGameButton.IsEnabled = false;
            }
        }

        /// <summary>
        /// Continues the game.
        /// </summary>
        private void releasePause()
        {
            pauseGameButton.Content = "Pause";
            stackPanel.Visibility = System.Windows.Visibility.Visible;
            pauseLabel.Visibility = System.Windows.Visibility.Collapsed;
            updateScreenThread.releasePause();
        }

        /// <summary>
        /// Pauses the game.
        /// </summary>
        private void pause()
        {
            pauseGameButton.Content = "Play";
            updateScreenThread.pause();
            pauseLabel.Visibility = System.Windows.Visibility.Visible;
            stackPanel.Visibility = System.Windows.Visibility.Collapsed;
        }

        /// <summary>
        /// Treats the key events.
        /// </summary>
        /// <param name="key">The key.</param>
        private void keyEvent(Key key)
        {
            switch (key)
            {
                case Key.Left:
                    if (!updateScreenThread.isPaused())
                    {
                        proxy.MoveLeftAsync();
                    }
                    break;
                case Key.Right:
                    if (!updateScreenThread.isPaused())
                    {
                        proxy.MoveRightAsync();
                    }
                    break;
                case Key.Down:
                    if (!updateScreenThread.isPaused())
                    {
                        proxy.MoveDownAsync();
                    }
                    break;
                case Key.Up:
                    if (!updateScreenThread.isPaused())
                    {
                        proxy.RotateAsync();
                    }
                    break;
                case Key.P:
                    pauseAndRelease();
                    break;
                default:
                    break;
            }
        }
    }
}
